package mt.comm;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;

import mt.Order;
import mt.comm.ClientSideMessage.Type;

public class ServerCommClass implements ServerComm {

	// Initialize class variables
	protected static final int PORT = 8080;       
	private ServerSocket serverSocket;
	private Map<String, ObjectOutputStream> clientMap = new HashMap<String, ObjectOutputStream>();
	private LinkedBlockingQueue<ServerSideMessage> lbQueue = new LinkedBlockingQueue<ServerSideMessage>();

	@Override
	public void start() {
		// Start a server socket
		try{
			serverSocket = new ServerSocket(PORT);
			System.out.println("SERVER --> ServerSocket Created: " + serverSocket);  
		} catch (IOException e) {
			System.out.println("Could not listen on port " + PORT);
			System.exit(-1);
		}

		new Thread(new Runnable(){

			public void run() {

				while(true)
				{	
					// Create a client connection using a runnable class, and create a thread for it
					ClientRunnable clientCon;     	
					try
					{
						System.out.println("SERVER --> Waiting for Client on port " + serverSocket.getLocalPort());
						clientCon = new ClientRunnable (serverSocket.accept());

						//System.out.println("SERVER --> Accepted connection");
						System.out.println("SERVER --> Just connected to " + clientCon.clientSocket.getRemoteSocketAddress());

						Thread t = new Thread(clientCon);
						t.start();
					} catch (IOException e) {
						System.out.println("Client connection failed");
						System.exit(-1);

					}
				}
			}
		}).start();
	}

	public class ClientRunnable implements Runnable {

		// Initialize class variables
		private Socket clientSocket;

		// Start input and output streams for the client, they will keep checking for new data inside a loop    		
		ObjectInputStream ois = null;
		ObjectOutputStream oos;

		public ClientRunnable(Socket clientSocket) {
			this.clientSocket = clientSocket;
		}

		@Override
		public void run() {

			try{
				ois = new ObjectInputStream(clientSocket.getInputStream());
				oos = new ObjectOutputStream(clientSocket.getOutputStream());   
			} catch (IOException e) {
				System.out.println("in or out failed");
				System.exit(-1);
			}
			
			while(clientSocket.isConnected()){
				try{
					ServerSideMessage serverMsg = (ServerSideMessage) ois.readObject();
					lbQueue.put(serverMsg);
					
					if ( serverMsg.getType() == mt.comm.ServerSideMessage.Type.CONNECTED ){
						clientMap.put(serverMsg.getSenderNickname(), oos);
						//System.out.println("serverComm --> " + serverMsg.getSenderNickname() + " is now connected");
					}
				} catch (InterruptedException e) {
					System.out.println("Interrupted Exception");
					e.printStackTrace();
					break;
				} catch (ClassNotFoundException e) {
					System.out.println("Class not found exception");
					e.printStackTrace();
					break;
				} catch (IOException e) {
					try {
						clientSocket.close();
					} catch (IOException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
					System.out.println("SERVER --> Closing client socket");
					//e.printStackTrace();
					break;
				}
			}
		}
	}

	@Override
	public ServerSideMessage getNextMessage() {
		//System.out.println("Server is getting messages...");
		// Take if there are any, pending messages from clients
		try {
			ServerSideMessage clientMsg = lbQueue.take();
			return clientMsg;
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null; 
	}

	@Override
	public boolean hasNextMessage() {
		// Check if there are any pending messages from clients
		return !(lbQueue.isEmpty());
	}

	@Override
	public void sendOrder(String receiversNickname, Order order) {

		// Compose the message to send
		Order newOrder = order;
		ClientSideMessage orderMsg = new ClientSideMessageClass(Type.ORDER, newOrder, receiversNickname, null);

		// Search the map for the appropriate client and add the message to his output stream
		ObjectOutputStream client = clientMap.get(receiversNickname);
		try {
			client.writeObject(orderMsg);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
	}    	

	@Override
	public void sendError(String toNickname, String error) {

		// Compose the message to send
		ClientSideMessage errorMsg = new ClientSideMessageClass(Type.ERROR, null, toNickname, error);

		// Search the map for the appropriate client and add the message to his output stream
		ObjectOutputStream client = clientMap.get(toNickname);
		try {
			client.writeObject(errorMsg);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
	}

	@Override
	public boolean clientIsConnected(String nickname) {

		// Check if the client is on the clientMap
		return clientMap.containsKey( nickname );
	}

	@Override
	public void disconnectClient(String nickname) {

		// Search the map for the appropriate client and disconnect his socket
		// Remove the client from the clientMap
		ObjectOutputStream client = clientMap.get (nickname);
		try {
			client.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		clientMap.remove(nickname);

		return;
	}

}